Run
Let's cubist start
and then run our token bridge!
Start Cubist chains and relayer
Now that we've built our contracts, we want to deploy them and then interact
with them. We're going to do this locally; the config (cubist-config.json
)
already specifies the URLs on which the chains will run.
The app config will look something like this:
{
...
"network_profiles": {
"default": {
"ethereum": { "url": "http://127.0.0.1:8545" },
"polygon": { "url": "http://127.0.0.1:9545" }
}
}
}
You can alter the config to specify other URLs if you choose.
To start both the local chains and the Cubist relayer, use:
cubist start
Note that this command may take a while (i.e., a minute) the first time you run it. This is because Cubist needs to download and build the local chain running services before it can start the chains.
Once you've run cubist start
, you'll see output that looks something like this:
Launching chains
ethereum ✔ [ 0s] [....................] http://localhost:8545/
polygon ✔ [ 1s] [....................] http://localhost:9545/ All servers available
Watching <path>/cubist-deploy dir
This output shows where the localnets are running (e.g., localhost:8545
),
and how long they took to become initialized and available. The output
also lets us know that the local relayer is successfully watching for the
events that tell it to relay information from one chain to another.
Alternatively, you can start the chains and the relayer seperately with
cubist chains start
and cubist relayer start
. For more information on
these and other Cubist CLI commands, try cubist help
, or check out the
CLI reference.
Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!
Run the app
Let's deploy and use our token bridge from within JavaScript or TypeScript.
(Advanced) deployment
First, let's deploy our TokenSender
and ERC20Bridged
contracts. This is a little more complex than previous
examples, since there's a circular dependency between
the two contracts: the TokenSender
stores a reference to
ERC20Bridged
, while ERC20Bridged
stores a reference to
TokenSender
. This circular reference is important because
it lets the token bridge "go both ways"; we can use it to
both buy and sell native tokens and ERC20 tokens.
One way of handling circular dependencies is to deploy one contract, deploy another with the first contract's address, and then update the first contract to include the second contract's address. We don't have to do that here. Instead, we're going to break the circular dependency by explicitly deploying our contracts and their generated shims separately.
This strategy works because the shims don't hold (circular) references to any other contracts; all they do is emit events when they are called. We can take advantage of the shims' simplicity to successfully deploy all of our contracts in sequence---without having to update them later. Here's the strategy we'll use:
- Deploy
ERC20Bridged
's shim (on Ethereum) - Deploy
TokenSender
to Ethereum (and its shim to Polygon), giving the constructor the address ofERC20Bridged
's shim - Deploy the real
ERC20Bridged
contract to Polygon, giving it the address of the TokenSender shim on Polygon.
The deployment code in deploy.js
(or ts) follows this strategy:
// Deploy the ERC20Bridged's shim (Ethereum)
const erc20bShims = await ERC20Bridged.deployShims();
// Get the ERC20Bridged's shim address on Ethereum
const erc20BridgedShim = erc20bShims.get(TokenSender.target());
// Deploy the TokenSender to Ethereum (and its shim to Polygon)
const tokenSender = await TokenSender.deploy(erc20BridgedShim.address);
// Deploy ERC20Bridged (to Polygon) with the TokenSender shim address (on Polygon).
// We already deployed all the shims (in this case only one), so we're going to call
// `deployWithShims` to add the native ERC20Bridged contract to the shim's permitted callers
const erc20Bridged = await ERC20Bridged.deployWithShims(erc20bShims,
"FooBarBaz", "FBB", tokenSender.addressOn(ERC20Bridged.target()));
First it deploys the ERC20Bridged
shim to Ethereum,
then TokenSender
(to Ethereum) and its shim
(to Polygon), and finally ERC20Bridged
(to Polygon).
Buy
The buy
script lets us transfer native (wei) tokens on Ethereum
for our FBB tokens on Polygon (with a conversion rate of 99.9%). We
specify receiverAccount
---a Polygon account for receiving
FBB---as input to the buy
script.
Let's look at the first few lines of buy
. These lines
use Cubist to get references to our deployed contracts and
the account associated with TokenSender
:
// Get new Cubist abstraction specific to this dapp
const cubist = new CubistORM();
// Get Cubist abstraction for the TokenSender contract
const TokenSender = cubist.TokenSender;
// Get Cubist abstraction for the ERC20Bridged contract
const ERC20Bridged = cubist.ERC20Bridged;
// Get chain-specific---in this case, Ethereum---abstraction
// for chain on which the TokenSender (non-shim) contract was deployed
const tsProject = TokenSender.project;
// Get Ethereum address associated with TokenSender
const sendingAccount = await tsProject.getSignerAddress();
// Get deployed TokenSender and ERC20 contract objects
const tokenSender = await TokenSender.deployed();
const erc20Bridged = await ERC20Bridged.deployed();
Next, we're going to use our contracts and the senderAccount
and
receiverAccount
to swap Ethereum tokens for FBB.
Recall that TokenSender
is deployed on Ethereum and ERC20Bridged
is deployed on Polygon:
// Get balance of receiver account on Polygon
const originalReceiverBalance = await erc20Bridged.inner.balanceOf(receivingAccount);
// Send from the TokenSender (on Ethereum) to our receiving account
await (await tokenSender.inner.bridgeSend(receivingAccount, { value: amount })).wait();
while (true) {
const newReceiverBalance = await erc20Bridged.inner.balanceOf(receivingAccount);
// Check if the receiving account has received the tokens
if (originalReceiverBalance.add(amount.sub(fee)).eq(newReceiverBalance)) {
console.log(`Received tokens! New balance: ${newReceiverBalance} fbb`);
break;
}
await sleep(1000);
}
This code snippet uses Cubist-generated binding functions to invoke
methods on both the TokenSender
and ERC20Bridged
smart contracts
(e.g., bridgeSend
and balanceOf
).
The script checks if the new balance in the receiving account is the old balance amount minus a few. In a real application, you'd account for concurrent transfers by, e.g., associating a transfer id with each transfer.
sell
is similar, but instead swaps in the other direction: FBB for wei.
Finally, balances
will show a table of balances in our accounts on
Ethereum and Polygon.